메서드 참조
1. 메서드 참조?
메서드 참조는 <ClassName>::<MethodName>
형태를 메서드 참조라고 한다.
메서드 하나를 호출하기 위해 사용한다.
이것은 메서드를 다른 메서드의 인자로 받거나 변수에 저장할 때 사용한다.
//메서드의 인자로 받을 때
something(200, system.out::println);
//변수에 저장할 때
Consumer<String> consumer = System.out::println;
위의 것들은 아래의 것과 동일하다
something(200, (v) -> {System.out.println(v);});
Consumer<String> consumer = (v) -> { System.out.println(v);};
그럼 이렇게 생각할 수도 있다. '메서드 참조는 람다식을 줄여 쓰는건가?'
답은 그럴 수도, 아닐 수도 있다. 조건이 하나 빠졌다. 그리고 조건이 하나 추가되어있다.
- 람다식을 쓸 때 람다 함수 내의 로직이 하나의 메서드를 호출하는 것이라면 람다식을 줄여 쓸 때 메서드 참조 형태를 사용할 수 있다.
- 메서드 하나를 호출하는 것이라면 메서드 참조 형태를 사용할 수 있다.
두 문장은 결국엔 같은 뜻이다. 그럼에도 두 문장을 모두 적은 이유는 핵심이 '메서드 하나를 호출' 하는 것에 있기 때문이다.
1-1. 메서드 하나를 호출하기까지
메서드 하나를 메서드참조 형태로 호출하기까지 개발자가 어떤 과정을 겪었는지 살펴볼 필요가 있다.
- 람다가 나오기 이전의 개발자는 인터페이스 콜백(익명 객체)의 형태로 메서드를 호출시켰다.
interface Callback {
void execute(String v);
}
void something(int number, Callback callback) {
callback.execute(String.valueOf(number))
}
something(200, new Callback() {
@Override
public void execute(String v) {
System.out.println(v)
}
})
- 람다가 나오면서 인터페이스를 만들지 않아도 되게 되었다.
void something(int number, Consumer<String> consumer) {
consumer.accept(String.valueOf(number))
}
something(200, (v) -> System.out.println(v));
- 람다를 메서드 참조로 바꿀 수 있게 되었다.
void something(int number, Consumer<String> consumer) {
consumer.accept(String.valueOf(number))
}
somethind(200, System.out::println);
여기까지 봐도 오 편하네. 싶긴 하지만 뭔가가 맘에 걸린다. 이 부분을 풀어야 이해가 된다.
1-2. 람다가 뭔데?
람다에 대한 설명은 잘 정리된 포스트들이 많으니 그것을 참고하도록 하고, 여기서 말하는 '람다는 뭔데?'는 '익명 객체를 어떻게 대체한건데?' 라는 측면에서 살펴볼 것이다.
람다는 변수에도 담을 수 있고, 매개변수로 넣어줄 수도 있다. 그러면 이것은 레퍼런스 타입의 '객체' 인건가?라는 생각에 도달했다면, 브릴리언트!
(자바에서 리턴 값, 매개변수는 모두 원시 타입 혹은 참조 타입이다. 이것은 앞으로도 영원히 거의 변할 수 없는 불변의 진리같은 것이다. 일급객체와 람다를 처음 이해하려고 했을 때 이것들이 개념을 초월한 무언가로 받아들여졌다. 무언가 이해할 수 없거나 도달할 수 없게 대단해 보인다면 그것이 원래 그런게 아니라 내가 알고있는걸 무시하고 생각하고 있거나, 내가 아직 알지 못하는 아주 기초적인 개념이 있던 적이 대부분이더라.)
그런데 객체라면 클래스도 있고, 그 안에 메서드도 있어야 하는데 람다는 항상 하나의 메서드의 형태를 띄는데? 그렇다. 메서드 하나를 가진 클래스를 만든다고 생각하면 된다. 실제로 람다를 구현 가능하게 하는 인터페이스들의 예시는 다음과 같다.
(두개 이상의 메서드를 갖는 인터페이스도 있지만, 추상 메서드는 하나뿐이다. This is a functional interface whose functional method is ___ 로 소개된 메서드가 람다를 구현하여 동작한다.)
@FunctionalInterface
public interface Runnable {
void run();
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t)
}
@FunctionalInterface
public interface Supplier<T> {
T get()
}
위의 인터페이스들은 람다를 매개변수로 받는 메서드를 보면 어떻게 사용되는지를 알 수 있다.
void something(int number, Consumer<String> consumer) {
consumer.accept(String.valueOf(number))
}
이렇게 인터페이스 타입에 맞는 람다 표현식을 쓰면 그것을 클래스 객체로 만들어서 구현해주고 그것의 메서드를 호출하게된다.
여기서 위와같은 인터페이스들을 '함수형 인터페이스' 라고 한다.
1-3. 람다안의 로직에 메서드 호출 하나만을 할 때만 메서드 참조를 쓸 수 있는게 아니라고?
이것을 설명하기 위해 1-2를 설명했다.
람다의 로직을 메서드 참조로 변경시킬 수 있는 이유는, 람다가 함수형 인터페이스를 만족시키는 객체이를 만들어내고 람다 로직 안에서 호출되는 하나의 메서드가 함수형 인터페이스의 메서드가 될 수 때문이다. 즉, 함수형 인터페이스를 만족시킬 수 있는 객체라면 메서드 참조를 사용할 수 있다는 말이 된다. 람다이기 때문에 메서드 참조로 바꿀 수 있는게 아니라, 람다가 메서드 하나를 실행시키기 때문에 메서드 참조로 바꿀 수 있는 것이다.
2. 메서드 참조 형태
메서드 참조 형태는 정적 메서드, 인스턴스 메서드, 매개변수의 메서드, 생성자 이렇게 4가지가 있다.
이것은 '어떤 클래스 혹은 어떤 객체'의 '어떤 메서드'를 참조하느냐에 관한 것이다.
위의 포스트에서 자세하게 설명해주시고 있으니 이것을 참고하고, 개인적으로 헷갈릴 수도 있는 부분만 원본 인용하고자 한다.
//매개변수 메서드
ArrayList<Number> list = new ArrayList<>();
Consumer<Collection<Number>> addElements;
// (x) -> obj.method(x)
addElements = (arr) -> list.addAll(arr);
// obj::method
addElements = list::addAll;
addElements.accept(List.of(1, 2, 3, 4, 5));
System.out.println(list); // [1, 2, 3, 4, 5]
//출처: https://inpa.tistory.com/entry/JAVA8-☕-람다식을-더-짧게-메소드-참조Method-Reference [Inpa Dev 👨💻:티스토리]